<?php

namespace Yolo\Service;

use Hyperf\Stringable\Str;
use Hyperf\Support\Filesystem\Filesystem;
use ReflectionMethod;
use ReflectionObject;
use Yolo\Service\Annotations\RESTFul;
use Yolo\Service\Annotations\RPC;
use Yolo\Service\Annotations\Tunnel;
use Yolo\Service\Enums\RPCType;
use Yolo\Utils\Path;

class Endpoint
{
    /**
     * @var array
     */
    private array $endpoints;

    private array $tunnelEndpoints;

    private array $restfulEndpoints;

    private array $internalEndpoints;

    public function __construct()
    {
        $this->endpoints = $this->getServiceEndpoints();

        $this->tunnelEndpoints = array_values(array_filter($this->endpoints, function($endpoint) {

            return $endpoint['type'] === RPCType::TUNNEL->value;
        }));

        $this->internalEndpoints = array_values(array_filter($this->endpoints, function($endpoint) {

            return $endpoint['type'] === RPCType::RPC->value;
        }));

        $this->restfulEndpoints = array_values(array_filter($this->endpoints, function($endpoint) {

            return $endpoint['type'] === RPCType::RESTFUL->value;
        }));
    }

    /**
     * 获取所有endpoint
     * @return array
     */
    public function getEndpoints(): array
    {
        return $this->endpoints;
    }

    /**
     * 获取tunnel endpoint
     * @return array
     */
    public function getTunnelEndPoints(): array
    {
        return $this->tunnelEndpoints;
    }

    /**
     * 获取内部 RPC endpoint
     * @return array
     */
    public function getInternalEndPoints(): array
    {
        return $this->internalEndpoints;
    }

    /**
     * 获取restful endpoint
     * @return array
     */
    public function getRestfulEndPoints(): array
    {
        return $this->restfulEndpoints;
    }

    private function getServiceEndpoints(): array
    {
        $types = [
            Tunnel::class,
            RPC::class,
            RESTFul::class,
        ];

        $classes = [
            "\\Yolo\\Service\\Apis\\ServiceApi",
        ];
        $filesystem = new Filesystem();
        foreach ($types as $type) {
            $type = Str::afterLast($type, '\\');
            $path = Path::getControllerAbsolutePath($type);
            if (! $filesystem->isDirectory($path)) {
                continue;
            }

            $files = $filesystem->files($path);
            if ($files) {
                foreach ($files as $file) {
                    $filename = $file->getFilename();
                    if (!Str::endsWith($filename, '.php')) {
                        continue;
                    }

                    // 拼接类名
                    $classes[] = "\\App\\Controller\\$type\\" . Str::substr($filename, 0, Str::length($filename) - 4);
                }
            }
        }

        $apis = [];
        foreach ($classes as $class) {
            $apis = array_merge($apis, $this->getClassEndpoints($class));
        }

        return $apis;
    }

    /**
     * 获取类接口
     * @param string $className 类名
     * @return array
     */
    private function getClassEndpoints(string $className): array
    {
        $apis = [];
        $class = new $className();
        $reflection = new ReflectionObject($class);

        foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            $attributes = $method->getAttributes();
//            $params = $method->getParameters();
//            $methodParams = [];
//            foreach ($params as $param) {
//                $type = $param->getType();
//                if ($type instanceof ReflectionUnionType) {
//                    throw new SystemException("Method parameter {$param->getName()} does not support union type in {$className}::{$method->getName()}.", -1);
//                }
//
//                $methodParams[] = [
//                    'name' => $param->getName(),
//                    'type' => $type ? $type->getName() : 'array',
//                ];
//            }
            foreach ($attributes as $attribute) {
                $attributeName = $attribute->getName();
                $arguments = $attribute->getArguments();
                if ($this->isApi($attributeName)) {
                    if ($attributeName === RESTFul::class) {
                        $attributeName = Str::afterLast($attributeName, '\\');
                        $options = $arguments[2];
                        $name = $options['name'] ?? '';
                        if (! $name) {
                            $options['name'] = ucfirst($method->getName());
                        }

                        $path = Path::formatPath($arguments[1]);

                        $apis[] = [
                            'type' => $attributeName,
                            'method' => $arguments[0],
                            'path' => $path,
                            'pathParsed' => Path::parseUri($path),
                            'invoke' => [
                                'controller' => $className,
                                'action' => $method->getName(),
//                                'params' => $methodParams,
                            ],
                            'options' => $options,
                        ];
                    } else {
                        $attributeName = Str::afterLast($attributeName, '\\');
                        $options = $arguments[0];
                        $name = $options['name'] ?? '';
                        if (! $name) {
                            $options['name'] = ucfirst($method->getName());
                        }
                        $apis[] = [
                            'type' => $attributeName,
                            'invoke' => [
                                'controller' => $className,
                                'action' => $method->getName(),
                            ],
                            'options' => $options,
                        ];
                    }

                    break;
                }
            }
        }

        return $apis;
    }

    private function isApi(string $attributeName): bool
    {
        return in_array($attributeName, [
            Tunnel::class,
            RPC::class,
            RESTFul::class,
        ]);
    }
}
